using System;
using System.Collections.Generic;
using System.Reflection;
using RaisingStudio.SmallBasicCompiler.Expressions;
using RaisingStudio.SmallBasicCompiler.Statements;

namespace RaisingStudio.SmallBasicCompiler.Engine
{
    public class ProgramTranslator
    {
        private int labelId = 10;
        public Compiler Compiler
        {
            get;
            private set;
        }
        public List<Instruction> ProgramInstructions
        {
            get;
            private set;
        }
        public Dictionary<string, List<Instruction>> SubroutineInstructions
        {
            get;
            private set;
        }
        public ProgramTranslator(Compiler compiler)
        {
            this.Compiler = compiler;
        }
        private string CreateNewLabel()
        {
            this.labelId += 10;
            return "__label" + this.labelId.ToString();
        }
        public void TranslateProgram()
        {
            this.ProgramInstructions = new List<Instruction>();
            this.SubroutineInstructions = new Dictionary<string, List<Instruction>>();
            this.TranslateStatements(this.ProgramInstructions, this.Compiler.Parser.ParseTree);
        }
        public void TranslateStatements(List<Instruction> instructions, List<Statement> statements)
        {
            foreach (Statement current in statements)
            {
                this.TranslateStatement(instructions, current);
            }
        }
        private void TranslateStatement(List<Instruction> instructions, Statement statement)
        {
            if (statement is AssignmentStatement)
            {
                this.TranslateAssignmentStatement(instructions, statement as AssignmentStatement);
                return;
            }
            if (statement is ForStatement)
            {
                this.TranslateForStatement(instructions, statement as ForStatement);
                return;
            }
            if (statement is GotoStatement)
            {
                this.TranslateGotoStatement(instructions, statement as GotoStatement);
                return;
            }
            if (statement is IfStatement)
            {
                this.TranslateIfStatement(instructions, statement as IfStatement);
                return;
            }
            if (statement is LabelStatement)
            {
                this.TranslateLabelStatement(instructions, statement as LabelStatement);
                return;
            }
            if (statement is MethodCallStatement)
            {
                this.TranslateMethodCallStatement(instructions, statement as MethodCallStatement);
                return;
            }
            if (statement is SubroutineCallStatement)
            {
                this.TranslateSubroutineCallStatement(instructions, statement as SubroutineCallStatement);
                return;
            }
            if (statement is SubroutineStatement)
            {
                this.TranslateSubroutineStatement(statement as SubroutineStatement);
                return;
            }
            if (statement is WhileStatement)
            {
                this.TranslateWhileStatement(instructions, statement as WhileStatement);
            }
        }
        private void TranslateAssignmentStatement(List<Instruction> instructions, AssignmentStatement statement)
        {
            IdentifierExpression identifierExpression = statement.LeftValue as IdentifierExpression;
            if (identifierExpression != null)
            {
                instructions.Add(new FieldAssignmentInstruction
                {
                    FieldName = identifierExpression.Identifier.NormalizedText,
                    LineNumber = statement.StartToken.Line,
                    RightSide = statement.RightValue
                });
                return;
            }
            PropertyExpression propertyExpression = statement.LeftValue as PropertyExpression;
            if (propertyExpression != null)
            {
                TypeInfo typeInfo = this.Compiler.TypeInfoBag.Types[propertyExpression.TypeName.NormalizedText];
                EventInfo eventInfo;
                if (typeInfo.Events.TryGetValue(propertyExpression.PropertyName.NormalizedText, out eventInfo))
                {
                    IdentifierExpression identifierExpression2 = statement.RightValue as IdentifierExpression;
                    if (identifierExpression2 != null)
                    {
                        instructions.Add(new EventAssignmentInstruction
                        {
                            EventInfo = eventInfo,
                            LineNumber = statement.StartToken.Line,
                            SubroutineName = identifierExpression2.Identifier.NormalizedText
                        });
                        return;
                    }
                }
                else
                {
                    PropertyInfo propertyInfo;
                    if (typeInfo.Properties.TryGetValue(propertyExpression.PropertyName.NormalizedText, out propertyInfo))
                    {
                        instructions.Add(new PropertyAssignmentInstruction
                        {
                            PropertyInfo = propertyInfo,
                            RightSide = statement.RightValue,
                            LineNumber = statement.StartToken.Line
                        });
                    }
                }
                return;
            }
            ArrayExpression arrayExpression = statement.LeftValue as ArrayExpression;
            if (arrayExpression != null)
            {
                instructions.Add(new ArrayAssignmentInstruction
                {
                    ArrayExpression = arrayExpression,
                    RightSide = statement.RightValue,
                    LineNumber = statement.StartToken.Line
                });
            }
        }
        private void TranslateForStatement(List<Instruction> instructions, ForStatement statement)
        {
            string labelName = this.CreateNewLabel();
            string labelName2 = this.CreateNewLabel();
            string labelName3 = this.CreateNewLabel();
            string labelName4 = this.CreateNewLabel();
            instructions.Add(new FieldAssignmentInstruction
            {
                FieldName = statement.Iterator.NormalizedText,
                RightSide = statement.InitialValue,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
            if (statement.StepValue != null)
            {
                instructions.Add(new IfGotoInstruction
                {
                    Condition = new BinaryExpression
                    {
                        LeftHandSide = new LiteralExpression
                        {
                            Literal = new TokenInfo
                            {
                                Token = Token.NumericLiteral,
                                Text = "0"
                            }
                        },
                        Operator = new TokenInfo
                        {
                            Token = Token.LessThan
                        },
                        RightHandSide = statement.StepValue
                    },
                    LabelName = labelName2,
                    LineNumber = statement.StartToken.Line
                });
                instructions.Add(new IfGotoInstruction
                {
                    Condition = new BinaryExpression
                    {
                        LeftHandSide = new IdentifierExpression
                        {
                            Identifier = statement.Iterator
                        },
                        Operator = new TokenInfo
                        {
                            Token = Token.LessThan
                        },
                        RightHandSide = statement.FinalValue
                    },
                    LabelName = labelName4,
                    LineNumber = statement.StartToken.Line
                });
                instructions.Add(new GotoInstruction
                {
                    LabelName = labelName3,
                    LineNumber = statement.StartToken.Line
                });
            }
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName2,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new IfGotoInstruction
            {
                Condition = new BinaryExpression
                {
                    LeftHandSide = new IdentifierExpression
                    {
                        Identifier = statement.Iterator
                    },
                    Operator = new TokenInfo
                    {
                        Token = Token.GreaterThan
                    },
                    RightHandSide = statement.FinalValue
                },
                LabelName = labelName4,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName3,
                LineNumber = statement.StartToken.Line
            });
            this.TranslateStatements(instructions, statement.ForBody);
            instructions.Add(new FieldAssignmentInstruction
            {
                FieldName = statement.Iterator.NormalizedText,
                RightSide = new BinaryExpression
                {
                    LeftHandSide = new IdentifierExpression
                    {
                        Identifier = statement.Iterator
                    },
                    Operator = new TokenInfo
                    {
                        Token = Token.Addition
                    },
                    RightHandSide = (statement.StepValue != null) ? statement.StepValue : new LiteralExpression
                    {
                        Literal = new TokenInfo
                        {
                            Token = Token.NumericLiteral,
                            Text = "1"
                        }
                    }
                },
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new GotoInstruction
            {
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName4,
                LineNumber = statement.StartToken.Line
            });
        }
        private void TranslateGotoStatement(List<Instruction> instructions, GotoStatement statement)
        {
            instructions.Add(new GotoInstruction
            {
                LabelName = statement.Label.NormalizedText,
                LineNumber = statement.StartToken.Line
            });
        }
        private void TranslateIfStatement(List<Instruction> instructions, IfStatement statement)
        {
            string labelName = this.CreateNewLabel();
            string labelName2 = this.CreateNewLabel();
            instructions.Add(new IfNotGotoInstruction
            {
                Condition = statement.Condition,
                LabelName = labelName2,
                LineNumber = statement.StartToken.Line
            });
            this.TranslateStatements(instructions, statement.ThenStatements);
            instructions.Add(new GotoInstruction
            {
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
            using (List<Statement>.Enumerator enumerator = statement.ElseIfStatements.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    ElseIfStatement elseIfStatement = (ElseIfStatement)enumerator.Current;
                    instructions.Add(new LabelInstruction
                    {
                        LabelName = labelName2,
                        LineNumber = statement.StartToken.Line
                    });
                    labelName2 = this.CreateNewLabel();
                    instructions.Add(new IfNotGotoInstruction
                    {
                        Condition = elseIfStatement.Condition,
                        LabelName = labelName2,
                        LineNumber = elseIfStatement.StartToken.Line
                    });
                    this.TranslateStatements(instructions, elseIfStatement.ThenStatements);
                    instructions.Add(new GotoInstruction
                    {
                        LabelName = labelName,
                        LineNumber = statement.StartToken.Line
                    });
                }
            }
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName2,
                LineNumber = statement.StartToken.Line
            });
            this.TranslateStatements(instructions, statement.ElseStatements);
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
        }
        private void TranslateLabelStatement(List<Instruction> instructions, LabelStatement statement)
        {
            instructions.Add(new LabelInstruction
            {
                LineNumber = statement.StartToken.Line,
                LabelName = statement.LabelToken.NormalizedText
            });
        }
        private void TranslateMethodCallStatement(List<Instruction> instructions, MethodCallStatement statement)
        {
            instructions.Add(new MethodCallInstruction
            {
                LineNumber = statement.StartToken.Line,
                MethodExpression = statement.MethodCallExpression
            });
        }
        private void TranslateSubroutineStatement(SubroutineStatement statement)
        {
            List<Instruction> list = new List<Instruction>();
            this.SubroutineInstructions[statement.SubroutineName.NormalizedText] = list;
            this.TranslateStatements(list, statement.SubroutineBody);
        }
        private void TranslateSubroutineCallStatement(List<Instruction> instructions, SubroutineCallStatement statement)
        {
            instructions.Add(new SubroutineCallInstruction
            {
                LineNumber = statement.StartToken.Line,
                SubroutineName = statement.SubroutineName.NormalizedText
            });
        }
        private void TranslateWhileStatement(List<Instruction> instructions, WhileStatement statement)
        {
            string labelName = this.CreateNewLabel();
            string labelName2 = this.CreateNewLabel();
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName2,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new IfNotGotoInstruction
            {
                Condition = statement.Condition,
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
            this.TranslateStatements(instructions, statement.WhileBody);
            instructions.Add(new GotoInstruction
            {
                LabelName = labelName2,
                LineNumber = statement.StartToken.Line
            });
            instructions.Add(new LabelInstruction
            {
                LabelName = labelName,
                LineNumber = statement.StartToken.Line
            });
        }
    }
}
